﻿#region Using

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Commons.Reflection.IL;
using SAPbouiCOM;
using SapFramework.Interface.Controls.Forms.Attributes;
using SapFramework.Interface.Events.EventCollections;
using SapFramework.Queries;

#endregion

namespace SapFramework.Interface.Events
{
    /// <summary>
    /// Classe que realiza o mapeamento dos eventos do SAP Business One utilizados no código
    /// </summary>
    /// <remarks>
    /// A classe poderia ter sido feita estática, mas por motivos de que num fúturo ela precise ser independente por conexão com a UI,
    /// deixei ela por instância.
    /// </remarks>
    public class EventListener
    {
        #region Flags Constantes - Para realizar a busca de métodos e propriedades

        protected const BindingFlags cFlagsMethodBase = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;

        protected const BindingFlags cFlagsEventInfo = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

        protected const BindingFlags cFlagsPropertyInfo = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

        protected const BindingFlags cFlagsMethodInfo = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

        #endregion

        #region Regex
        /// <summary>
        /// Separa um resultado do regex por quebra de linhas.
        /// </summary>
        protected static readonly Regex srRgxGetLines = new Regex( @".+$", RegexOptions.Multiline );
        /// <summary>
        /// Contém regexes específicos para a recuperação do código dos itens de um formulário.
        /// </summary>
        protected static class RegexOnItemAdd
        {
            /// <summary>
            /// Identifica o bloco de código de uma função que contenha a declaração de um item do formulário.
            /// </summary>
            /// <example>
            /// 0016 : ldstr  "1"
            /// 0021 : callvirt instance SapFramework.Interface.Controls.Items.Specific.Button SapFramework.Interface.Controls.Items.Base.Items::Add()
            /// 0026 : call instance System.Void PadaOneTest.Interface.SapForm::set_BtnOk()
            /// </example>
            public static readonly Regex srFindItemAddBlock =
                new Regex( @"^\d{4}\s:\sldstr.+$(\r\n?|\n)" +
                           @"^\d{4}\s:\scallvirt.+Items::Add\(\)\s$(\r\n?|\n)" +
                           @"^\d{4}\s:\scall\sinstance.+$",
                           RegexOptions.Multiline );
            /// <summary>
            /// Recupera a chave única do item.
            /// </summary>
            /// <remarks>
            /// Usar com a linha de índice 0 do resultado do Regex RegexOnItemAdd.srFindItemAddBlock.
            /// </remarks>
            /// <example>
            /// 0016 : ldstr  "1" => 1
            /// </example>
            public static readonly Regex srGetItemUniqueId = new Regex( "(?<=\")\\w+(?=\")" );
            /// <summary>
            /// Recuper o nome da propriedade a qual este código se refere.
            /// </summary>
            /// <remarks>
            /// Usar com a linha de índice 2 do resultado do Regex RegexOnItemAdd.srFindItemAddBlock.
            /// </remarks>
            /// <example>
            /// 0026 : call instance System.Void PadaOneTest.Interface.SapForm::set_BtnOk() => BtnOk
            /// </example>
            public static readonly Regex srGetItemPropertyName = new Regex( @"(?<=set_)\w+(?=\(\))" );
        }
        /// <summary>
        /// Contém regexes específicos para a recuperação de eventos de um item item de formulário.
        /// </summary>
        protected static class RegexOnEventAdd
        {
            /// <summary>
            /// Identifica o bloco de código de uma função que contenha a declaração de um item do formulário.
            /// </summary>
            /// <example>
            /// 0033 : call instance SapFramework.Interface.Controls.Items.Specific.Button PadaOneTest.Interface.SapForm::get_BtnOk()
            /// 0038 : ldarg.0
            /// 0040 : ldftn instance System.Void PadaOneTest.Interface.SapForm::BtnOk_OnAfterItemPressed()
            /// 0045 : newobj instance void SapFramework.Interface.Events.EventVoidItem::.ctor()
            /// 0050 : callvirt instance System.Void SapFramework.Interface.Controls.Items.Specific.Button::add_OnAfterItemPressed()
            /// </example>
            public static readonly Regex srFindEventAddBlock =
                new Regex( @"^\d{4}\s:\s(call|).+$(\r\n?|\n)" +
                           @"^\d{4}\s:\s.+$(\r\n?|\n)" +
                           @"^\d{4}\s:\sldftn.+$(\r\n?|\n)" +
                           @"^\d{4}\s:\s.+$(\r\n?|\n)" +
                           @"^\d{4}\s:\s.+$",
                           RegexOptions.Multiline );
            /// <summary>
            /// Separa o nome da propriedade na classe do formulário, que representa um item de formulário do SAP Business One.
            /// </summary>
            /// <remarks>
            /// Usar com a linha de índice 0 do resultado do Regex RegexOnItemAdd.srFindEventAddBlock.
            /// </remarks>
            /// <example>
            /// 0033 : call instance SapFramework.Interface.Controls.Items.Specific.Button PadaOneTest.Interface.SapForm::get_BtnOk() => BtnOk
            /// </example>
            public static readonly Regex srGetItemPropertyName = new Regex( @"(?<=get_)\w+" );
            /// <summary>
            /// Separa o nome do método que representa o evento que deverá ser chamado na classe EventHandler.
            /// </summary>
            /// <remarks>
            /// Usar com a linha de índice 2 do resultado do Regex RegexOnItemAdd.srFindEventAddBlock.
            /// </remarks>
            /// <example>
            /// 0040 : ldftn instance System.Void PadaOneTest.Interface.SapForm::BtnOk_OnAfterItemPressed() => BtnOk_OnAfterItemPressed
            /// </example>
            public static readonly Regex srGetMethodName = new Regex( @"(?<=::)\w+" );
            /// <summary>
            /// Separa o nome da propriedade do evento do formulário ou botão que será invocado.
            /// </summary>
            /// <remarks>
            /// Usar com a linha de índice 4 do resultado do Regex RegexOnItemAdd.srFindEventAddBlock.
            /// </remarks>
            /// <example>
            /// 0050 : callvirt instance System.Void SapFramework.Interface.Controls.Items.Specific.Button::add_OnAfterItemPressed() => OnAfterItemPressed
            /// </example>
            public static readonly Regex srGetEventPropertyName = new Regex( @"(?<=::add_)\w+" );
            /// <summary>
            /// Separa o nome da classe que é responsável pelo evento.
            /// </summary>
            /// <remarks>
            /// Usar com a linha de índice 4 do resultado do Regex RegexOnItemAdd.srFindEventAddBlock.
            /// </remarks>
            /// <example>
            /// 0050 : callvirt instance System.Void SapFramework.Interface.Controls.Items.Specific.Button::add_OnAfterItemPressed() => SapFramework.Interface.Controls.Items.Specific.Button
            /// </example>
            public static readonly Regex srGetEventOwnerType = new Regex( @"[a-zA-z.]+(?=::)" );
            /// <summary>
            /// Verifica se o tipo do retorno do evento é <c>void</c>. Se o regex não encontrar nada é porque o retorno é <c>bool</c>.
            /// </summary>
            /// <remarks>
            /// Usar com a linha de índice 4 do resultado do Regex RegexOnItemAdd.srFindEventAddBlock.
            /// </remarks>
            /// <example>
            /// 0050 : callvirt instance System.Void SapFramework.Interface.Controls.Items.Specific.Button::add_OnAfterItemPressed() => System.Void
            /// </example>
            public static readonly Regex srGetEventReturnType = new Regex( @"System.Void" );
        }

        #endregion

        /// <summary>
        /// Construtor
        /// </summary>
        public EventListener()
        {
            #region Iniciando variaveis

            DctFormsAndMethods = new Dictionary<Type, List<string>>();
            DctFormsAndDctOfItemByCodeAndType = new Dictionary<Type, Dictionary<PropertyInfo, string>>();
            FormEvents = new FormEventDictionary();

            DctMenusAndMethods = new Dictionary<Type, List<string>>();
            MenuEvents = new MenuEventDictionary();

            Filters = new FiltersDictionary();

            #endregion

            #region Processando formulários

            RecoverTypeAndItsMethods( FormQueries.GetPadaOneForms(), DctFormsAndMethods, DctFormsAndDctOfItemByCodeAndType );

            this.RecoverFormsAndItsItemsPropertiesAndUniqueIds();

            this.RecoverFormItemEvents();

            #endregion

            #region Processando menus

            RecoverTypeAndItsMethods( MenuQueries.GetPadaOneMenus(), DctMenusAndMethods );

            this.RecoverMenuEvents();

            #endregion
        }

        private static void RecoverTypeAndItsMethods( IEnumerable<Type> aEnmTypes, Dictionary<Type, List<string>> aDctTypeMethods, Dictionary<Type, Dictionary<PropertyInfo, string>> aDctTypePropertyItems = null )
        {
            //Itera por estes menus, recuperando uma representação textual de seus métodos e construtores.
            foreach ( Type lType in aEnmTypes )
            {
                //Lista que vai ter a representação textual dos métodos.
                List<string> lLstMethod = new List<string>();
                //Função anonima que extrai a parte textual dos métodos.
                Action<MethodBase> lActCreateTuples = ( aMethodInfo ) =>
                {
                    string lStrMethod = IlReader.GetMethodBodyCode( aMethodInfo ).ToString();
                    lLstMethod.Add( lStrMethod );
                };
                //Extrai a parte textual dos construtores.
                lType.GetConstructors( cFlagsMethodBase ).ToList().ForEach( lActCreateTuples );
                //Extrai a parte textual dos métodos.
                lType.GetMethods( cFlagsMethodBase ).ToList().ForEach( lActCreateTuples );
                //Adiciona a parte textual ao dicionário.
                aDctTypeMethods.Add( lType, lLstMethod );
                //Aproveita a deixa e inicializa o dicionário por tipo  de item (Cada property é um item, praticamente).
                if ( aDctTypePropertyItems != null )
                    aDctTypePropertyItems.Add( lType, new Dictionary<PropertyInfo, string>() );
            }
        }

        #region Métodos para o tratamento de formulários

        /// <summary>
        /// Busca em cada um dos métodos encontrados em RecoverTypeAndItsMethods(Form) pelos códigos dos itens do SAP Business One
        /// que foram mapeados nos formulários.
        /// </summary>
        /// <remarks>
        /// Exemplo de um código em não-assembly buscado por esta função seria:
        /// <c>BtnOk = Items.Add[Button]( "1" );</c>
        /// </remarks>
        private void RecoverFormsAndItsItemsPropertiesAndUniqueIds()
        {
            foreach ( KeyValuePair<Type, List<string>> lKvFormMethod in DctFormsAndMethods )
            {
                foreach ( string lMethod in lKvFormMethod.Value )
                {
                    foreach ( Match lMatch in RegexOnItemAdd.srFindItemAddBlock.Matches( lMethod ) )
                    {
                        MatchCollection lLines = srRgxGetLines.Matches( lMatch.Value );

                        Commons.Diagnostics.Debug.Assert.Equals( () => lLines.Count, 3 );

                        string lItemPropertyName = RegexOnItemAdd.srGetItemPropertyName.Match( lLines[2].Value ).Value;

                        PropertyInfo lItemProperty = lKvFormMethod.Key.GetProperty( lItemPropertyName, cFlagsPropertyInfo );

                        string lItemUniqueId = RegexOnItemAdd.srGetItemUniqueId.Match( lLines[0].Value ).Value;

                        DctFormsAndDctOfItemByCodeAndType[lKvFormMethod.Key].Add( lItemProperty, lItemUniqueId );
                    }
                }
            }
        }
        /// <summary>
        /// Busca em cada um dos métodos encontrados em RecoverTypeAndItsMethods(Form) pelos eventos dos itens do SAP Business One
        /// que foram mapeados nos formulários.
        /// </summary>
        /// <remarks>
        /// Exemplo de um código em não-assembly buscado por esta função seria:
        /// <c>BtnOk.OnAfterItemPressed += BtnOk_OnAfterItemPressed;</c>
        /// </remarks>
        private void RecoverFormItemEvents()
        {
            foreach ( KeyValuePair<Type, List<string>> lKvFormMethod in DctFormsAndMethods )
            {
                foreach ( string lMethod in lKvFormMethod.Value )
                {
                    foreach ( Match lMatch in RegexOnEventAdd.srFindEventAddBlock.Matches( lMethod ) )
                    {
                        MatchCollection lLines = srRgxGetLines.Matches( lMatch.Value );

                        Commons.Diagnostics.Debug.Assert.Equals( () => lLines.Count, 5 );

                        #region Recupera a string do tipo do formulário no SAP Business One.

                        string lSboFormType = ( (FormTypeAttribute)lKvFormMethod.Key.GetCustomAttributes( typeof( FormTypeAttribute ), false ).Single() ).Type;

                        #endregion

                        #region Recupera o Id único do Item.

                        Match lMatchItemPropertyName = RegexOnEventAdd.srGetItemPropertyName.Match( lLines[0].Value );
                        //Por padrão, quando não encontrar, deixa o id do item vazio, que equivale a um evento do formulário.
                        string lItemUniqueId = "";
                        //Verifica se existe um código do item...
                        if( lMatchItemPropertyName.Success )
                        {
                            string lItemPropertyName = lMatchItemPropertyName.Value;

                            PropertyInfo lItemPropery = lKvFormMethod.Key.GetProperty( lItemPropertyName, cFlagsPropertyInfo );

                            Commons.Diagnostics.Contract.Assert.NotNull( () => lItemPropery );

                            lItemUniqueId = DctFormsAndDctOfItemByCodeAndType[lKvFormMethod.Key][lItemPropery];
                        }

                        #endregion

                        #region Recupera o tipo do evento do SAP Business One.

                        Type lOwnerType = Type.GetType( RegexOnEventAdd.srGetEventOwnerType.Match( lLines[4].Value ).Value );

                        string lEventPropertyName = RegexOnEventAdd.srGetEventPropertyName.Match( lLines[4].Value ).Value;

                        EventInfo lOwnerEventInfo = lOwnerType.GetEvent( lEventPropertyName, cFlagsEventInfo );

                        Commons.Diagnostics.Contract.Assert.NotNull( () => lOwnerEventInfo );

                        EventAttribute lOwnerEventAttribute = (EventAttribute)lOwnerEventInfo.GetCustomAttributes( typeof( EventAttribute ), true ).Single();

                        #endregion

                        #region Recupera o tipo do valor retornado pelo evento

                        Match lMatchEventReturnType = RegexOnEventAdd.srGetEventReturnType.Match( lLines[2].Value );

                        bool lIsActionBefore = !lMatchEventReturnType.Success;

                        #endregion

                        #region Recupera o método que deverá ser invocado quando este evento ocorrer

                        string lMethodName = RegexOnEventAdd.srGetMethodName.Match( lLines[2].Value ).Value;

                        MethodInfo lMethodInfo = lKvFormMethod.Key.GetMethod( lMethodName, cFlagsMethodInfo );

                        Commons.Diagnostics.Contract.Assert.NotNull( () => lMethodInfo );

                        #endregion

                        FormEvents.Add( lSboFormType, lItemUniqueId, lOwnerEventAttribute.Type, lIsActionBefore, lMethodInfo );

                        Filters[lOwnerEventAttribute.Type].Add( lSboFormType );
                    }
                }
            }
        }
        
        #endregion

        #region Métodos para o tratamento de menus
        /// <summary>
        /// Busca em cada um dos métodos encontrados em RecoverTypeAndItsMethods(Menu) pelos eventos de menus que foram mapeados.
        /// </summary>
        /// <remarks>
        /// Exemplo de um código em não-assembly buscado por esta função seria:
        /// <c>this.OnBeforeClick += SapMenu_OnBeforeClick;</c>
        /// </remarks>
        private void RecoverMenuEvents()
        {
            foreach ( KeyValuePair<Type, List<string>> lKvMenuMethods in DctMenusAndMethods )
            {
                foreach ( string lMethod in lKvMenuMethods.Value )
                {
                    foreach ( Match lMatch in RegexOnEventAdd.srFindEventAddBlock.Matches( lMethod ) )
                    {
                        MatchCollection lLines = srRgxGetLines.Matches( lMatch.Value );

                        Commons.Diagnostics.Debug.Assert.Equals( () => lLines.Count, 5 );

                        #region Recupera a string do tipo do formulário no SAP Business One.

                        string lSboMenuType = MenuQueries.GetMenuTypeAttribute( lKvMenuMethods.Key ).Type;

                        #endregion

                        #region Recupera o tipo do evento do SAP Business One.

                        string lEventPropertyName = RegexOnEventAdd.srGetEventPropertyName.Match( lLines[4].Value ).Value;

                        EventInfo lMenuEventInfo = lKvMenuMethods.Key.GetEvent( lEventPropertyName, cFlagsEventInfo );

                        Commons.Diagnostics.Contract.Assert.NotNull( () => lMenuEventInfo );

                        EventAttribute lOwnerEventAttribute = (EventAttribute)lMenuEventInfo.GetCustomAttributes( typeof( EventAttribute ), true ).Single();

                        #endregion

                        #region Recupera o tipo do valor retornado pelo evento

                        Match lMatchEventReturnType = RegexOnEventAdd.srGetEventReturnType.Match( lLines[2].Value );

                        bool lIsActionBefore = !lMatchEventReturnType.Success;

                        #endregion

                        #region Recupera o método que deverá ser invocado quando este evento ocorrer

                        string lMethodName = RegexOnEventAdd.srGetMethodName.Match( lLines[2].Value ).Value;

                        MethodInfo lMethodInfo = lKvMenuMethods.Key.GetMethod( lMethodName, cFlagsMethodInfo );

                        Commons.Diagnostics.Contract.Assert.NotNull( () => lMethodInfo );

                        #endregion

                        MenuEvents.Add( lSboMenuType, lOwnerEventAttribute.Type, lIsActionBefore, lMethodInfo );

                        Filters[lOwnerEventAttribute.Type].Add( lSboMenuType );
                    }
                }
            }
        }
        
        #endregion

        /// <summary>
        /// Separa internamente as classes que herdam de <see cref="SapFramework.Interface.Controls.Forms.Base.Form"/> e seus respectivos métodos.
        /// </summary>
        protected Dictionary<Type, List<string>> DctFormsAndMethods { get; private set; }
        /// <summary>
        /// Separa internamente as classes que herdam de <see cref="SapFramework.Interface.Controls.Forms.Base.Form"/> 
        /// e também um par conténdo as propriedades e códigos do SBO que representam um item.
        /// </summary>
        protected Dictionary<Type, Dictionary<PropertyInfo, string>> DctFormsAndDctOfItemByCodeAndType { get; private set; }
        /// <summary>
        /// Contém os eventos de formulário que foram corretamente mapeados.
        /// </summary>
        /// <remarks>
        /// Utilize ItemUniqueId igual a vazio para representar um evento do formulário.
        /// </remarks>
        public FormEventDictionary FormEvents { get; private set; }
        /// <summary>
        /// Separa internamente as classes que herdam de <see cref="SapFramework.Interface.Controls.Menus.Base.Menu"/> e seus respectivos métodos.
        /// </summary>
        protected Dictionary<Type, List<string>> DctMenusAndMethods { get; private set; }
        /// <summary>
        /// Contém os eventos de menus que foram corretamente mapeados.
        /// </summary>
        public MenuEventDictionary MenuEvents { get; private set; }
        /// <summary>
        /// Coleção de dados que realiza a montagem dos filtros de formulário para usar no SAP Business One.
        /// </summary>
        public FiltersDictionary Filters { get; private set; }
    }
}